﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using HeartLess.LevelData;

namespace HeartLess.GameObjects
{
    public abstract class CollidableGameObject : GameObject
    {
        private static IList<CollidableGameObject> _collidableBarriers;
        private static IList<CollidableGameObject> _collidableTriggers;
        
        private GameTime _gameTime;

        private Vector2 _gravityVelocity;
        private static readonly Vector2 GRAVITY_ACCELERATION = new Vector2(0, 30);
        private static readonly int TERMINAL_VELOCITY = 600;

        private bool _gravityEnabled;
        protected bool GravityEnabled
        {
            get { return _gravityEnabled; }
            set
            {
                _gravityEnabled = value;
                if (_gravityEnabled)
                {
                    IsAirbourne = true;
                    _gravityVelocity = Vector2.Zero;
                }
            }
        }

        protected bool IsCollisionListener
        {
            set;
            get;
        }

        protected bool IsAirbourne
        {
            get;
            private set;
        }

        protected IList<CollidableGameObject> CollidableTriggers
        {
            get { return _collidableTriggers; }
        }

        static CollidableGameObject()
        {
            _collidableBarriers = new List<CollidableGameObject>();
            _collidableTriggers = new List<CollidableGameObject>();
        }

        public CollidableGameObject()
            :base()
        {
            //do nothing
        }

        public CollidableGameObject(Vector2 location)
            : base(location)
        {
            //do nothing
        }

        public CollidableGameObject(Point gridLocation)
            : base(gridLocation)
        {
            //do nothing
        }

        public override void Update(GameTime gameTime)
        {
            _gameTime = gameTime;

            if (_gravityEnabled)
            {
                applyGravity();
            }

            if (IsCollisionListener)
            {
                CheckCollisionTriggers();
            }
        }

        public void applyGravity()
        {
            if (_gravityVelocity.Y < TERMINAL_VELOCITY)
            {
                _gravityVelocity += GRAVITY_ACCELERATION;
            }


            bool validMove = tryMove(_gravityVelocity);

            IsAirbourne = (Math.Abs(_gravityVelocity.Y) > 4 * GRAVITY_ACCELERATION.Y)
                           && validMove;
        }

        public bool tryMove(Vector2 velocity)
        {
            if ((GravityEnabled) && (velocity != _gravityVelocity) && (velocity.Y != 0))
            {
                _gravityVelocity = new Vector2(0, velocity.Y);
            }
      
            int deltaX;
            int deltaY;

            deltaX = (int)(velocity.X * _gameTime.ElapsedGameTime.Milliseconds) / 1000;
            deltaY = (int)(velocity.Y * _gameTime.ElapsedGameTime.Milliseconds) / 1000;

            if (-1 <= deltaX && deltaX <= 1 && -1<= deltaY && deltaY <= 1) 
                return true;

            if (willCollide(deltaX, deltaY))
            {
                fineTuneCollision(0, 0, ref deltaX, ref deltaY);

                if (velocity == _gravityVelocity)
                {
                    _gravityVelocity = Vector2.Zero;
                    if (velocity.Y >0)
                    {
                        IsAirbourne = false;
                    }
                }
            }

            if (willCollide(deltaX, deltaY) == false)
            {
                Location += new Vector2(deltaX, deltaY);
            }

            if (deltaX != 0 || deltaY != 0)
            {
                onMove();
                return true;
            }
            return false;
        }

        protected abstract void onMove();

        protected abstract void handleCollision(CollidableGameObject collidedObject);

        public static void Unload()
        {
            _collidableBarriers.Clear();
            _collidableTriggers.Clear();
        }

        protected static void addCollisionBarrier(CollidableGameObject collidableGameObject)
        {
            _collidableBarriers.Add(collidableGameObject);
        }

        protected static void removeCollisionBarrier(CollidableGameObject collidableGameObject)
        {
            _collidableBarriers.Remove(collidableGameObject);
        }

        protected static void addCollisionTrigger(CollidableGameObject collidableGameObject)
        {
            _collidableTriggers.Add(collidableGameObject);
        }

        protected static void removeCollisionTrigger(CollidableGameObject collidableGameObject)
        {
            _collidableTriggers.Remove(collidableGameObject);
        }

        private bool willCollide(int deltaX, int deltaY)
        {
            if (deltaX == 0 && deltaY == 0) return false;

            if (deltaY == 0)
            {
                Bounds.Offset(0, -Bounds.Y % 100);
            }

            Rectangle futureBounds = new Rectangle(Bounds.X + deltaX, 
                                                   Bounds.Y + deltaY, 
                                                   Bounds.Width,
                                                   Bounds.Height);

            foreach (CollidableGameObject otherObject in _collidableBarriers)
            {
                if (futureBounds.Intersects(otherObject.Bounds))
                {
                    if (otherObject.IsCollisionListener)
                    {
                        otherObject.handleCollision(this);
                    }
                    return true;
                }
            }
            return false;
        }

        //adjusts velocities to have pixel-perfect collisions. 
        private void fineTuneCollision(int x, int y, ref int deltaX, ref int deltaY)
        {
            if (deltaX == 0 && deltaY == 0) return;

            int adjustingDeltaX = deltaX / 2;
            int adjustingDeltaY = deltaY / 2;

            if (willCollide(x + deltaX, y + deltaY))
            {
                fineTuneCollision(x, y, ref adjustingDeltaX, ref adjustingDeltaY);
            }
            else
            {
                fineTuneCollision(x + adjustingDeltaX, y + adjustingDeltaY, ref adjustingDeltaX, ref adjustingDeltaY);
            }

            deltaX = x + adjustingDeltaX;
            deltaY = y + adjustingDeltaY;

        }

        private void CheckCollisionTriggers()
        {
            foreach (CollidableGameObject collidableObject in _collidableTriggers)
            {
                if (this == collidableObject) continue;

                if (this.Bounds.Intersects(collidableObject.Bounds))
                {
                    handleCollision(collidableObject);
                }
            }
        }
    }
}
